// ==UserScript==
// @name         离线网页保存助手-电脑端
// @version      1.0
// @description  将网页保存为完整HTML格式（支持图片转Base64），增强版
// @author       DeepSeek+Cursor
// @match        *://*/*
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_addValueChangeListener
// @grant        GM_notification
// @grant        GM_setClipboard
// @grant        GM_openInTab
// @connect      *
// @icon         https://gitee.com/selling-surprises/Greasyfork-Script-repository/raw/main/%E5%AE%9E%E7%94%A8%E8%84%9A%E6%9C%AC/%E4%BF%9D%E5%AD%98%E4%B8%BA%E7%A6%BB%E7%BA%BF%E7%BD%91%E9%A1%B5/saveWebPageOffline.png
// @updateURL    https://gitee.com/selling-surprises/Greasyfork-Script-repository/raw/main/%E5%AE%9E%E7%94%A8%E8%84%9A%E6%9C%AC/%E4%BF%9D%E5%AD%98%E4%B8%BA%E7%A6%BB%E7%BA%BF%E7%BD%91%E9%A1%B5/%E4%BF%9D%E5%AD%98%E4%B8%BA%E7%A6%BB%E7%BA%BF%E7%BD%91%E9%A1%B5-%E7%94%B5%E8%84%91%E7%AB%AF.user.js
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // 版本信息
    const VERSION = '1.2';
    const UPDATE_INFO = '全新浮动按钮UI，优化进度显示，保留广告拦截功能';

    // 设置键名
    const SETTING_KEY = 'floatingButtonEnabled';
    const IMAGE_CONVERSION_KEY = 'imageConversionEnabled';
    
    // 添加按钮样式
    GM_addStyle(`
        #offline-saver-container {
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 10000;
            display: flex;
            flex-direction: column;
            align-items: flex-end;
            transition: opacity 0.3s;
        }
        
        #offline-saver-container.hidden {
            opacity: 0;
            pointer-events: none;
        }
        
        #offline-saver-btn {
            width: 60px;
            height: 60px;
            border-radius: 50% !important;
            background: linear-gradient(135deg, #6a11cb, #2575fc);
            color: white;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 4px 15px rgba(0,0,0,0.2);
            border: none;
            font-size: 24px;
            cursor: pointer;
            transition: transform 0.3s;
            min-width: 60px;
            min-height: 60px;
            max-width: 60px;
            max-height: 60px;
            padding: 0;
        }
        
        #offline-saver-btn:active {
            transform: scale(0.95);
        }
        
        #offline-saver-panel {
            background: white;
            border-radius: 15px;
            padding: 15px;
            box-shadow: 0 5px 25px rgba(0,0,0,0.15);
            width: 280px;
            margin-top: 10px;
            display: none;
        }
        
        .offline-status {
            padding: 10px;
            border-radius: 8px;
            margin: 10px 0;
            font-size: 14px;
            text-align: center;
        }
        
        .offline-processing {
            background: #e3f2fd;
            color: #0d47a1;
        }
        
        .offline-success {
            background: #e8f5e9;
            color: #2e7d32;
        }
        
        .offline-error {
            background: #ffebee;
            color: #c62828;
        }
        
        .offline-progress {
            height: 8px;
            background: #e0e0e0;
            border-radius: 4px;
            overflow: hidden;
            margin: 10px 0;
        }
        
        .offline-progress-bar {
            height: 100%;
            background: linear-gradient(90deg, #6a11cb, #2575fc);
            width: 0%;
            transition: width 0.3s;
        }
        
        .setting-row {
            display: flex;
            align-items: center;
            margin: 10px 0;
        }
        
        .setting-label {
            flex: 1;
            font-size: 14px;
            color: #333;
        }
        
        .setting-toggle {
            position: relative;
            display: inline-block;
            width: 50px;
            height: 24px;
        }
        
        .setting-toggle input {
            opacity: 0;
            width: 0;
            height: 0;
        }
        
        .setting-toggle-slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #ccc;
            transition: .4s;
            border-radius: 24px;
        }
        
        .setting-toggle-slider:before {
            position: absolute;
            content: "";
            height: 18px;
            width: 18px;
            left: 3px;
            bottom: 3px;
            background-color: white;
            transition: .4s;
            border-radius: 50%;
        }
        
        input:checked + .setting-toggle-slider {
            background: linear-gradient(90deg, #6a11cb, #2575fc);
        }
        
        input:checked + .setting-toggle-slider:before {
            transform: translateX(26px);
        }
        
        .offline-feature-btn {
            width: 100%;
            padding: 8px;
            margin: 5px 0;
            background: #f5f5f5;
            border: 1px solid #ddd;
            border-radius: 5px;
            cursor: pointer;
            text-align: center;
            font-size: 13px;
            transition: all 0.2s;
        }
        
        .offline-feature-btn:hover {
            background: #e9e9e9;
            transform: translateY(-2px);
        }
        
        @media (max-width: 480px) {
            #offline-saver-container {
                bottom: 10px;
                right: 10px;
            }
            
            #offline-saver-btn {
                width: 50px;
                height: 50px;
            }
            
            #offline-saver-panel {
                width: 250px;
                padding: 12px;
            }
        }
    `);

    // 添加FontAwesome
    if (!document.querySelector('link[href*="font-awesome"]')) {
        const faLink = document.createElement('link');
        faLink.rel = 'stylesheet';
        faLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css';
        document.head.appendChild(faLink);
    }

    // 创建浮动按钮
    function createFloatingButton() {
        // 创建容器
        const container = document.createElement('div');
        container.id = 'offline-saver-container';
        document.body.appendChild(container);
        
        // 创建按钮
        const button = document.createElement('button');
        button.id = 'offline-saver-btn';
        button.innerHTML = '<i class="fas fa-download"></i>';
        button.title = '保存为HTML';
        container.appendChild(button);
        
        // 创建面板
        const panel = document.createElement('div');
        panel.id = 'offline-saver-panel';
        container.appendChild(panel);
        
        // 面板内容
        panel.innerHTML = `
            <h3 style="margin-top: 0; text-align: center; font-size: 18px;">离线保存助手</h3>
            <div class="setting-row">
                <span class="setting-label">图片转Base64（离线可用）</span>
                <label class="setting-toggle">
                    <input type="checkbox" id="image-conversion-toggle" ${GM_getValue(IMAGE_CONVERSION_KEY, true) ? 'checked' : ''}>
                    <span class="setting-toggle-slider"></span>
                </label>
            </div>
            <div style="margin: 15px 0;">
                <button id="offline-start-btn" style="width: 100%; padding: 10px; background: linear-gradient(135deg, #6a11cb, #2575fc); color: white; border: none; border-radius: 5px; cursor: pointer; font-weight: bold;">开始保存</button>
            </div>
            <div class="offline-progress-container" style="display: none;">
                <div class="offline-progress">
                    <div class="offline-progress-bar"></div>
                </div>
                <div class="offline-status"></div>
            </div>
            <div class="offline-features" style="margin-top: 10px; border-top: 1px solid #eee; padding-top: 10px;">
                <div class="offline-feature-btn" id="toggle-info-display">切换信息显示</div>
                <div class="offline-feature-btn" id="element-picker">元素选择器</div>
            </div>
        `;
        
        // 切换面板显示
        button.addEventListener('click', function() {
            if (panel.style.display === 'block') {
                panel.style.display = 'none';
            } else {
                panel.style.display = 'block';
            }
        });
        
        // 图片转换设置切换
        const imageConversionToggle = panel.querySelector('#image-conversion-toggle');
        imageConversionToggle.addEventListener('change', function() {
            GM_setValue(IMAGE_CONVERSION_KEY, this.checked);
        });
        
        // 保存按钮事件
        const startBtn = panel.querySelector('#offline-start-btn');
        const progressContainer = panel.querySelector('.offline-progress-container');
        const progressBar = panel.querySelector('.offline-progress-bar');
        const statusEl = panel.querySelector('.offline-status');
        
        startBtn.addEventListener('click', async function() {
            const convertImages = GM_getValue(IMAGE_CONVERSION_KEY, true);
            startBtn.disabled = true;
            startBtn.textContent = '处理中...';
            progressContainer.style.display = 'block';
            statusEl.style.display = 'block';
            statusEl.className = 'offline-status offline-processing';
            statusEl.textContent = '正在准备页面...';
            progressBar.style.width = '0%';
            
            try {
                // 调用保存功能
                await savePageAsHTML(convertImages, (progress, message) => {
                    progressBar.style.width = `${progress}%`;
                    statusEl.textContent = message;
                });
                
                statusEl.className = 'offline-status offline-success';
                statusEl.innerHTML = '<i class="fas fa-check-circle"></i> 保存成功！';
                
                // 3秒后重置按钮
                setTimeout(() => {
                    startBtn.disabled = false;
                    startBtn.textContent = '开始保存';
                    progressBar.style.width = '0%';
                    statusEl.style.display = 'none';
                    progressContainer.style.display = 'none';
                }, 3000);
                
            } catch (error) {
                statusEl.className = 'offline-status offline-error';
                statusEl.textContent = `错误: ${error.message || error}`;
                startBtn.disabled = false;
                startBtn.textContent = '重试';
                console.error('保存错误:', error);
            }
        });
        
        // 功能按钮事件
        panel.querySelector('#toggle-info-display').addEventListener('click', toggleInfoDisplay);
        panel.querySelector('#element-picker').addEventListener('click', startElementPicker);
        
        return { container, button, panel };
    }

    // 显示简单消息
    function showSimpleMessage(message) {
        const messageDiv = document.createElement('div');
        messageDiv.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 15px 20px;
            border-radius: 5px;
            z-index: 9999999;
            font-family: Arial, sans-serif;
            font-size: 14px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
        `;
        messageDiv.textContent = message;
        document.body.appendChild(messageDiv);
        
        setTimeout(() => {
            messageDiv.remove();
        }, 3000);
    }

    // 切换离线信息显示
    function toggleInfoDisplay() {
        const currentSetting = GM_getValue('showInfoDisplay', true);
        GM_setValue('showInfoDisplay', !currentSetting);
        
        if (currentSetting) {
            showSimpleMessage('已关闭离线页面信息显示');
        } else {
            showSimpleMessage('已开启离线页面信息显示');
        }
    }

    // ======================== 元素选择器模块 ========================
    let pickerActive = false;
    let highlightedElement = null;
    let pickerRoot = null;
    let pickerOverlay = null;
    
    // 启动元素选择器
    function startElementPicker() {
        if (pickerActive) return;
        
        pickerActive = true;
        showSimpleMessage('元素选择器已启动，点击要拦截的元素');
        
        // 创建透明覆盖层
        pickerOverlay = document.createElement('div');
        pickerOverlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: transparent;
            z-index: 2147483647;
            cursor: crosshair;
        `;
        document.body.appendChild(pickerOverlay);
        
        // 创建选择器UI
        createPickerUI();
        
        // 设置事件处理
        pickerOverlay.addEventListener('mousemove', onPickerMouseMove);
        pickerOverlay.addEventListener('click', onPickerClick);
        
        // 右键退出
        pickerOverlay.addEventListener('contextmenu', function(e) {
            e.preventDefault();
            stopElementPicker();
            return false;
        });
        
        // ESC键退出
        document.addEventListener('keydown', function(e) {
            if (e.key === 'Escape' && pickerActive) {
                stopElementPicker();
            }
        });
    }
    
    // 创建选择器UI
    function createPickerUI() {
        pickerRoot = document.createElement('div');
        pickerRoot.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            z-index: 2147483647;
            padding: 5px 10px;
            background: rgba(0, 0, 0, 0.7);
            color: white;
            font-family: Arial, sans-serif;
            font-size: 12px;
            border-radius: 0 0 4px 0;
            pointer-events: none;
        `;
        
        pickerRoot.innerHTML = `
            <div style="margin: 5px 0;">
                <p style="margin: 0 0 5px 0;">正在选择元素，点击要拦截的元素</p>
                <p style="margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 300px;" id="element-picker-selected-element">鼠标悬停在元素上显示选择器</p>
            </div>
            <div style="margin: 5px 0; font-size: 10px;">
                <p style="margin: 0;">按ESC键或右键点击取消</p>
            </div>
        `;
        
        document.body.appendChild(pickerRoot);
    }
    
    // 处理选择器鼠标移动
    function onPickerMouseMove(event) {
        if (!pickerActive) return;
        
        const x = event.clientX;
        const y = event.clientY;
        pickerOverlay.style.pointerEvents = 'none';
        const element = document.elementFromPoint(x, y);
        pickerOverlay.style.pointerEvents = 'auto';
        
        if (element && element !== pickerOverlay && !pickerRoot.contains(element)) {
            highlightElement(element);
        }
    }
    
    // 处理选择器点击事件
    function onPickerClick(event) {
        if (!pickerActive) return;
        
        event.preventDefault();
        event.stopPropagation();
        
        const x = event.clientX;
        const y = event.clientY;
        pickerOverlay.style.pointerEvents = 'none';
        const element = document.elementFromPoint(x, y);
        pickerOverlay.style.pointerEvents = 'auto';
        
        if (element && element !== pickerOverlay && !pickerRoot.contains(element)) {
            confirmElementBlock(element);
            stopElementPicker();
        }
        
        return false;
    }
    
    // 停止元素选择器
    function stopElementPicker() {
        if (highlightedElement) {
            highlightedElement.style.outline = '';
            highlightedElement = null;
        }
        
        if (pickerRoot) {
            document.body.removeChild(pickerRoot);
            pickerRoot = null;
        }
        
        if (pickerOverlay) {
            pickerOverlay.removeEventListener('mousemove', onPickerMouseMove);
            pickerOverlay.removeEventListener('click', onPickerClick);
            document.body.removeChild(pickerOverlay);
            pickerOverlay = null;
        }
        
        pickerActive = false;
    }
    
    // 高亮元素
    function highlightElement(element) {
        if (highlightedElement) {
            highlightedElement.style.outline = '';
        }
        
        highlightedElement = element;
        highlightedElement.style.outline = '2px solid #f00';
        updatePickerUI(element);
    }
    
    // 更新选择器UI
    function updatePickerUI(element) {
        if (!pickerRoot) return;
        
        const selector = generateSelector(element);
        const elementInfo = document.getElementById('element-picker-selected-element');
        
        if (elementInfo) {
            elementInfo.textContent = selector;
        }
    }
    
    // 生成CSS选择器
    function generateSelector(element) {
        if (!element || element === document.body || element === document.documentElement) {
            return 'body';
        }

        // 检查是否是广告弹窗类元素
        const isPopupAd = isAdPopup(element);
        if (isPopupAd) {
            return isPopupAd;
        }
        
        // 1. 尝试使用ID
        if (element.id) {
            const idSelector = `#${element.id}`;
            if (document.querySelectorAll(idSelector).length === 1) {
                return idSelector;
            }
        }
        
        // 2. 尝试使用类名组合
        if (element.className && typeof element.className === 'string') {
            const classes = element.className.trim().split(/\s+/);
            if (classes.length > 0 && classes[0]) {
                if (classes.length > 1) {
                    const classSelector = `.${classes.join('.')}`;
                    if (document.querySelectorAll(classSelector).length === 1) {
                        return classSelector;
                    }
                }
                
                for (const cls of classes) {
                    if (/ad|banner|popup|modal|dialog|overlay/i.test(cls)) {
                        const singleClassSelector = `.${cls}`;
                        if (document.querySelectorAll(singleClassSelector).length < 5) {
                            return singleClassSelector;
                        }
                    }
                }
                
                const singleClassSelector = `.${classes[0]}`;
                if (document.querySelectorAll(singleClassSelector).length < 3) {
                    return singleClassSelector;
                }
            }
        }
        
        // 3. 尝试使用标签+文本内容
        const tagName = element.tagName.toLowerCase();
        const text = element.textContent ? element.textContent.trim() : '';
        if (text && /广告|赞助|推广|ad|sponsor|advertisement/i.test(text)) {
            return `${tagName}:has-text(${text.substring(0, 10)})`;
        }
        
        // 4. 尝试使用HTML结构特征
        if (hasAdAttributes(element)) {
            let attributeSelector = tagName;
            const adAttributes = hasAdAttributes(element);
            for (const attr in adAttributes) {
                if (adAttributes[attr]) {
                    attributeSelector += `[${attr}="${adAttributes[attr]}"]`;
                }
            }
            return attributeSelector;
        }
        
        // 5. 如果有高z-index或fixed/absolute定位
        const computedStyle = window.getComputedStyle(element);
        const position = computedStyle.position;
        const zIndex = parseInt(computedStyle.zIndex);
        
        if ((position === 'fixed' || position === 'absolute') && zIndex > 1000) {
            return `${tagName}[style*="position: ${position}"]`;
        }
        
        // 6. 如果父元素有ID
        const parent = element.parentElement;
        if (parent && parent.id && parent !== document.body) {
            return `#${parent.id} > ${tagName}`;
        }
        
        // 7. 创建路径选择器
        return createPathSelector(element);
    }
    
    // 检查元素是否有广告特征属性
    function hasAdAttributes(element) {
        const adAttributeMap = {};
        const attributes = [
            'data-ad', 'data-ad-client', 'data-ad-slot', 'data-adtest', 'data-ad-unit',
            'data-adblock', 'data-adbox', 'data-advertisement', 'aria-label', 'role'
        ];
        
        for (const attr of attributes) {
            if (element.hasAttribute(attr)) {
                adAttributeMap[attr] = element.getAttribute(attr);
            }
        }
        
        return Object.keys(adAttributeMap).length > 0 ? adAttributeMap : false;
    }
    
    // 检查元素是否是广告弹窗
    function isAdPopup(element) {
        let current = element;
        let level = 0;
        
        while (current && level < 3) {
            if (current.textContent && /^广告$|^AD$|^\s*广告\s*$/.test(current.textContent.trim())) {
                return `*:has(${current.tagName.toLowerCase()}:contains(广告))`;
            }
            
            const style = window.getComputedStyle(current);
            if (
                (style.position === 'fixed' || style.position === 'absolute') &&
                parseInt(style.zIndex) > 1000 &&
                current.offsetWidth > 100 && current.offsetHeight > 100
            ) {
                const closeButtons = current.querySelectorAll('button, [role="button"], [aria-label*="关闭"], [aria-label*="close"]');
                if (closeButtons.length > 0) {
                    return `${current.tagName.toLowerCase()}[style*="position: ${style.position}"]`;
                }
            }
            
            if (current.className && /ad|popup|modal|dialog/i.test(current.className)) {
                return `.${current.className.split(' ')[0]}`;
            }
            
            if (current.id && /ad|popup|modal|dialog/i.test(current.id)) {
                return `#${current.id}`;
            }
            
            current = current.parentElement;
            level++;
        }
        
        return false;
    }
    
    // 确认屏蔽元素
    function confirmElementBlock(element) {
        if (!element) return;
        
        const selector = generateSelector(element);
        showBlockConfirmDialog(element, selector);
    }
    
    // 显示屏蔽确认对话框
    function showBlockConfirmDialog(element, selector) {
        const dialog = document.createElement('div');
        dialog.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            border: 1px solid #ccc;
            border-radius: 8px;
            padding: 20px;
            z-index: 2147483647;
            box-shadow: 0 4px 12px rgba(0,0,0,0.15);
            width: 350px;
            max-width: 90%;
            font-family: Arial, sans-serif;
            color: #333;
        `;
        
        dialog.innerHTML = `
            <h3 style="margin-top: 0; color: #333; font-size: 16px;">拦截网页元素</h3>
            <div style="margin: 10px 0;">
                <p style="margin: 5px 0; font-size: 14px;">是否在保存网页时移除这个元素？</p>
                <p style="margin: 5px 0; font-size: 12px; color: #666; word-break: break-all; max-height: 60px; overflow-y: auto;">${selector}</p>
            </div>
            <div style="margin: 10px 0;">
                <label style="display: flex; align-items: center; user-select: none; cursor: pointer; margin-bottom: 8px;">
                    <input type="checkbox" id="block-immediate" checked style="margin-right: 8px;"> 
                    <span>立即隐藏元素</span>
                </label>
            </div>
            <div style="display: flex; justify-content: flex-end; gap: 10px; margin-top: 15px;">
                <button id="block-cancel" style="padding: 8px 12px; border: none; background: #f0f0f0; border-radius: 4px; cursor: pointer;">取消</button>
                <button id="block-confirm" style="padding: 8px 12px; border: none; background: #4285f4; color: white; border-radius: 4px; cursor: pointer;">确认</button>
            </div>
        `;
        
        document.body.appendChild(dialog);
        
        const immediateCheckbox = document.getElementById('block-immediate');
        const cancelButton = document.getElementById('block-cancel');
        const confirmButton = document.getElementById('block-confirm');
        
        cancelButton.addEventListener('click', function() {
            document.body.removeChild(dialog);
            element.style.display = '';
            element.style.visibility = '';
            element.style.opacity = '';
        });
        
        confirmButton.addEventListener('click', function() {
            if (immediateCheckbox.checked) {
                element.style.display = 'none';
            }
            
            if (!window._tempHiddenSelectors) {
                window._tempHiddenSelectors = [];
            }
            
            if (!window._tempHiddenSelectors.includes(selector)) {
                window._tempHiddenSelectors.push(selector);
                showSimpleMessage(`已添加元素到当前保存会话`);
            }
            
            document.body.removeChild(dialog);
        });
    }
    
    // 创建路径选择器
    function createPathSelector(element) {
        let selector = element.tagName.toLowerCase();
        let current = element;
        let path = [];
        
        for (let i = 0; i < 4; i++) {
            const parent = current.parentElement;
            if (!parent || parent === document.body || parent === document.documentElement) {
                break;
            }
            
            if (parent.id) {
                path.unshift(`#${parent.id}`);
                break;
            } else if (parent.className && typeof parent.className === 'string') {
                const classes = parent.className.trim().split(/\s+/);
                if (classes.length > 0 && classes[0]) {
                    path.unshift(`.${classes[0]}`);
                    break;
                }
            } else {
                const siblings = Array.from(parent.children).filter(c => c.tagName === current.tagName);
                if (siblings.length > 1) {
                    const index = siblings.indexOf(current) + 1;
                    path.unshift(`${parent.tagName.toLowerCase()} > ${current.tagName.toLowerCase()}:nth-of-type(${index})`);
                } else {
                    path.unshift(parent.tagName.toLowerCase());
                }
            }
            
            current = parent;
        }
        
        if (path.length === 0) {
            const index = Array.from(element.parentElement.children).indexOf(element) + 1;
            return `${element.tagName.toLowerCase()}:nth-child(${index})`;
        }
        
        return path.join(' > ') + (path.length ? ' > ' : '') + selector;
    }

    // 主保存函数
    async function savePageAsHTML(convertImages, progressCallback) {
        progressCallback(10, '正在复制文档...');
        
        // 复制整个文档
        const docClone = document.cloneNode(true);
        
        // 移除浮动按钮容器
        const containerClone = docClone.getElementById('offline-saver-container');
        if (containerClone) {
            containerClone.parentNode.removeChild(containerClone);
        }
        
        // 确保正确的中文编码
        ensureProperEncoding(docClone);
        
        progressCallback(20, '应用屏蔽规则...');
        applyCustomBlockRules(docClone);
        
        progressCallback(30, '处理样式表...');
        await processStylesheets(docClone);
        
        if (convertImages) {
            progressCallback(40, '处理图片...');
            await processImages(docClone);
        }
        
        progressCallback(60, '处理脚本...');
        processScripts(docClone);
        
        progressCallback(70, '处理链接...');
        processLinks(docClone);
        
        progressCallback(80, '清理文档...');
        cleanupDocument(docClone);
        
        progressCallback(90, '处理广告元素...');
        handleBlockedAds(docClone);
        
        progressCallback(95, '添加保存信息...');
        addSaveInfo(docClone);
        
        progressCallback(98, '生成HTML...');
        const html = getDocumentHTML(docClone);
        
        // 下载HTML文件
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        const pageTitle = document.title || 'webpage';
        let filename = sanitizeFilename(pageTitle) + '_' + timestamp + '.html';
        
        if (filename.length > 240) {
            filename = filename.substring(0, 236) + '.html';
        }
        
        const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
        const fileSize = blob.size;
        
        // 使用GM_download下载
        GM_download({
            url: URL.createObjectURL(blob),
            name: filename,
            saveAs: true,
            onload: function() {
                progressCallback(100, '下载完成');
            },
            onerror: function(e) {
                throw new Error(`下载失败: ${e.error}`);
            }
        });
        
        return true;
    }

    // 应用自定义屏蔽规则
    function applyCustomBlockRules(doc) {
        const customSelectors = GM_getValue('customHiddenSelectors', []);
        const tempSelectors = window._tempHiddenSelectors || [];
        const allSelectors = [...customSelectors, ...tempSelectors];
        
        if (allSelectors.length === 0) return;
        
        allSelectors.forEach(selector => {
            try {
                const elements = doc.querySelectorAll(selector);
                elements.forEach(el => {
                    if (el) {
                        el.remove();
                    }
                });
            } catch (e) {
                console.error(`应用选择器 "${selector}" 时出错:`, e);
            }
        });
    }

    // 确保正确的中文编码
    function ensureProperEncoding(doc) {
        let metaCharset = doc.querySelector('meta[charset]');
        if (!metaCharset) {
            metaCharset = doc.querySelector('meta[http-equiv="Content-Type"]');
            if (!metaCharset) {
                metaCharset = doc.createElement('meta');
                metaCharset.setAttribute('charset', 'UTF-8');
                const head = doc.getElementsByTagName('head')[0];
                if (head && head.firstChild) {
                    head.insertBefore(metaCharset, head.firstChild);
                } else if (head) {
                    head.appendChild(metaCharset);
                }
            }
        } else {
            metaCharset.setAttribute('charset', 'UTF-8');
        }
        
        const htmlTag = doc.getElementsByTagName('html')[0];
        if (htmlTag) {
            const currentLang = htmlTag.getAttribute('lang');
            if (!currentLang && /[\u4e00-\u9fa5]/.test(doc.body.innerText)) {
                htmlTag.setAttribute('lang', 'zh-CN');
            }
        }
    }
    
    // 处理被广告屏蔽器隐藏的元素
    function handleBlockedAds(doc) {
        const possibleAdSelectors = [
            '.adsbygoogle',
            '[id*="google_ads"]',
            '[id*="aswift"]',
            '[id*="ad-"]',
            '[class*="ad-"]',
            '[class*="ads-"]',
            '[id*="ads-"]',
            'ins[class*="adsbygoogle"]',
            'div[data-ad]',
            'div[id*="div-gpt-ad"]',
            'div[class*="banner"]',
            'iframe[src*="googleads"]',
            'iframe[src*="doubleclick"]',
            'iframe[data-src*="ads"]',
            'iframe[id*="google_ads"]',
            '[style*="z-index: 2147483647"]',
            '[class*="advert"]',
            '[id*="sponsor"]',
            '[class*="sponsor"]'
        ];
        
        try {
            const adElements = Array.from(doc.querySelectorAll(possibleAdSelectors.join(',')));
            
            adElements.forEach(el => {
                if (el.offsetWidth > 0 || el.offsetHeight > 0) {
                    const computedStyle = getComputedStyle(el);
                    if (computedStyle.display === 'none' || 
                        computedStyle.visibility === 'hidden' || 
                        computedStyle.opacity === '0') {
                        el.remove();
                    }
                } else {
                    el.remove();
                }
            });
            
            const placeholders = Array.from(doc.querySelectorAll('div[style*="background-color: rgba(0, 0, 0, 0); position: absolute;"][style*="height: "][style*="width: "]'));
            placeholders.forEach(el => el.remove());
            
            const emptyDivs = Array.from(doc.querySelectorAll('div:empty'));
            emptyDivs.forEach(div => {
                const className = div.className || '';
                const id = div.id || '';
                if (/ad|ads|banner|sponsor|promo/i.test(className) || /ad|ads|banner|sponsor|promo/i.test(id)) {
                    div.remove();
                }
            });
        } catch (e) {
            console.error("处理广告元素时出错:", e);
        }
    }

    // 处理样式表
    async function processStylesheets(doc) {
        const styleSheets = Array.from(doc.querySelectorAll('link[rel="stylesheet"]'));
        
        for (const stylesheet of styleSheets) {
            try {
                const href = stylesheet.getAttribute('href');
                if (!href || href.startsWith('data:')) continue;
                
                const fullUrl = new URL(href, window.location.href).href;
                const cssText = await fetchResource(fullUrl);
                
                const style = doc.createElement('style');
                style.textContent = await processCSSText(cssText, fullUrl);
                stylesheet.parentNode.replaceChild(style, stylesheet);
            } catch (error) {
                console.error("处理样式表时出错:", error);
                const style = doc.createElement('style');
                style.textContent = `/* 无法加载样式: ${stylesheet.getAttribute('href')} */`;
                stylesheet.parentNode.replaceChild(style, stylesheet);
            }
        }
        
        const styles = Array.from(doc.querySelectorAll('style'));
        for (const style of styles) {
            try {
                style.textContent = await processCSSText(style.textContent, window.location.href);
            } catch (error) {
                console.error("处理内联样式时出错:", error);
            }
        }
    }

    // 处理CSS文本中的URL
    async function processCSSText(cssText, baseUrl) {
        const urlRegex = /url\(\s*(['"]?)([^'"()]+)\1\s*\)/g;
        const urls = [...cssText.matchAll(urlRegex)];
        
        for (const match of urls) {
            try {
                const fullUrlMatch = match[0];
                const quote = match[1];
                const urlPath = match[2];
                
                if (urlPath.startsWith('data:')) continue;
                
                const fullUrl = new URL(urlPath, baseUrl).href;
                const base64Data = await fetchResourceAsBase64(fullUrl);
                cssText = cssText.replace(fullUrlMatch, `url(${quote}${base64Data}${quote})`);
            } catch (error) {
                console.error("处理CSS中的URL时出错:", error);
            }
        }
        
        return cssText;
    }

    // 处理图片
    async function processImages(doc) {
        const images = Array.from(doc.querySelectorAll('img'));
        const maxConcurrent = 5;
        const chunks = [];
        
        for (let i = 0; i < images.length; i += maxConcurrent) {
            chunks.push(images.slice(i, i + maxConcurrent));
        }
        
        for (const chunk of chunks) {
            await Promise.allSettled(chunk.map(async (img) => {
                try {
                    const src = img.getAttribute('src');
                    if (!src || src.startsWith('data:')) return;
                    
                    const fullUrl = new URL(src, window.location.href).href;
                    const base64Data = await fetchResourceAsBase64(fullUrl);
                    
                    img.setAttribute('src', base64Data);
                    
                    if (img.hasAttribute('srcset')) {
                        img.removeAttribute('srcset');
                    }
                    
                    if (img.hasAttribute('data-src')) {
                        img.removeAttribute('data-src');
                    }
                    if (img.hasAttribute('loading')) {
                        img.removeAttribute('loading');
                    }
                } catch (error) {
                    console.error("处理图片时出错:", error);
                    if (img.hasAttribute('alt')) {
                        img.setAttribute('title', '图片加载失败: ' + img.getAttribute('alt'));
                    }
                }
            }));
            
            await new Promise(resolve => setTimeout(resolve, 100));
        }
        
        const elementsWithBgImage = Array.from(doc.querySelectorAll('*[style*="background"]'));
        for (const element of elementsWithBgImage) {
            try {
                const style = element.getAttribute('style');
                if (!style) continue;
                
                const urlRegex = /url\(\s*(['"]?)([^'"()]+)\1\s*\)/g;
                const urls = [...style.matchAll(urlRegex)];
                
                let newStyle = style;
                for (const match of urls) {
                    const fullUrlMatch = match[0];
                    const quote = match[1];
                    const urlPath = match[2];
                    
                    if (urlPath.startsWith('data:')) continue;
                    
                    const fullUrl = new URL(urlPath, window.location.href).href;
                    const base64Data = await fetchResourceAsBase64(fullUrl);
                    
                    newStyle = newStyle.replace(fullUrlMatch, `url(${quote}${base64Data}${quote})`);
                }
                
                element.setAttribute('style', newStyle);
            } catch (error) {
                console.error("处理背景图片时出错:", error);
            }
        }
    }

    // 处理脚本
    function processScripts(doc) {
        const externalScripts = Array.from(doc.querySelectorAll('script[src]'));
        for (const script of externalScripts) {
            script.remove();
        }
        
        const inlineScripts = Array.from(doc.querySelectorAll('script:not([src])'));
        for (const script of inlineScripts) {
            if (/google|analytics|tracker|tracking|ads|advertisement|facebook|twitter/i.test(script.textContent)) {
                script.remove();
            }
        }
    }

    // 处理链接
    function processLinks(doc) {
        const links = Array.from(doc.querySelectorAll('a[href]'));
        for (const link of links) {
            const href = link.getAttribute('href');
            if (!href) continue;
            
            if (href.startsWith('#') || href.startsWith('javascript:')) continue;
            
            if (href.startsWith('http') || href.startsWith('//')) {
                link.setAttribute('target', '_blank');
                link.setAttribute('rel', 'noopener noreferrer');
            }
        }
    }

    // 添加保存信息
    function addSaveInfo(doc) {
        if (GM_getValue('showInfoDisplay', true) === false) {
            return;
        }
        
        const infoDiv = doc.createElement('div');
        infoDiv.id = 'offline-save-info';
        infoDiv.style.cssText = `
            position: fixed;
            bottom: 0;
            right: 0;
            background: rgba(0, 0, 0, 0.6);
            color: white;
            padding: 5px 10px;
            font-size: 12px;
            border-top-left-radius: 5px;
            z-index: 9999;
            opacity: 0.5;
            transition: opacity 0.3s, transform 0.3s;
            transform: translateY(0);
            cursor: pointer;
            font-family: Arial, sans-serif;
        `;
        
        const today = new Date();
        const saveDate = today.toLocaleString();
        const originalUrl = window.location.href;
        const originalTitle = document.title;
        
        const miniInfoDiv = doc.createElement('div');
        miniInfoDiv.style.cssText = `
            padding: 3px 6px;
            text-align: center;
        `;
        miniInfoDiv.textContent = '↑ 离线页面信息';
        
        const detailsDiv = doc.createElement('div');
        detailsDiv.id = 'offline-save-details';
        detailsDiv.style.cssText = `
            padding: 5px;
            display: none;
        `;
        
        detailsDiv.innerHTML = `
            <div>标题: ${originalTitle}</div>
            <div>来源: <a href="${originalUrl}" target="_blank" style="color:#aaf;text-decoration:underline;">${originalUrl}</a></div>
            <div>保存时间: ${saveDate}</div>
            <div style="text-align:right;font-size:10px;margin-top:5px;">
                <a href="#" id="remove-info-bar" style="color:#aaf;text-decoration:underline;">关闭</a>
            </div>
        `;
        
        infoDiv.appendChild(detailsDiv);
        infoDiv.appendChild(miniInfoDiv);
        
        infoDiv.addEventListener('mouseenter', function() {
            this.style.opacity = '1';
        });
        
        infoDiv.addEventListener('mouseleave', function() {
            if (detailsDiv.style.display === 'none') {
                this.style.opacity = '0.5';
            }
        });
        
        infoDiv.addEventListener('click', function(e) {
            e.stopPropagation();
            
            if (e.target.id === 'remove-info-bar') {
                e.preventDefault();
                this.remove();
                return;
            }
            
            if (detailsDiv.style.display === 'none') {
                detailsDiv.style.display = 'block';
                miniInfoDiv.textContent = '↓ 收起信息';
                this.style.opacity = '1';
            } else {
                detailsDiv.style.display = 'none';
                miniInfoDiv.textContent = '↑ 离线页面信息';
                this.style.opacity = '0.5';
            }
        });
        
        const allLinks = detailsDiv.querySelectorAll('a');
        for (const link of allLinks) {
            link.addEventListener('click', function(e) {
                e.stopPropagation();
            });
        }
        
        doc.body.appendChild(infoDiv);
    }

    // 清理文档
    function cleanupDocument(doc) {
        const nodeIterator = doc.createNodeIterator(
            doc,
            NodeFilter.SHOW_COMMENT,
            { acceptNode: () => NodeFilter.FILTER_ACCEPT }
        );
        
        let node;
        while (node = nodeIterator.nextNode()) {
            node.parentNode.removeChild(node);
        }
        
        const tempElementIds = [
            'offline-saver-message', 
            'offline-save-button',
            'force-download-link',
            'force-save-msg',
            'dismiss-message'
        ];
        
        tempElementIds.forEach(id => {
            const tempElement = doc.getElementById(id);
            if (tempElement) {
                tempElement.remove();
            }
        });
        
        const tempSelectors = [
            '[id^="force-download-link-"]',
            '[class^="offline-message"]',
            '.offline-saver-notification',
            '[id^="offline-"]'
        ];
        
        tempSelectors.forEach(selector => {
            const elements = Array.from(doc.querySelectorAll(selector));
            elements.forEach(el => {
                if (el.id !== 'offline-save-info' && el.id !== 'offline-save-details') {
                    el.remove();
                }
            });
        });
        
        const hiddenElements = Array.from(doc.querySelectorAll('[hidden], [style*="display: none"], [style*="display:none"], [style*="visibility: hidden"], [style*="visibility:hidden"]'));
        for (const element of hiddenElements) {
            if (element.tagName === 'TEMPLATE' || element.tagName === 'NOSCRIPT' || element.id === 'offline-save-details') continue;
            element.remove();
        }
    }

    // 获取文档HTML
    function getDocumentHTML(doc) {
        let metaCharset = doc.querySelector('meta[charset]');
        if (!metaCharset) {
            const head = doc.head || doc.getElementsByTagName('head')[0];
            if (head) {
                metaCharset = doc.createElement('meta');
                metaCharset.setAttribute('charset', 'UTF-8');
                head.insertBefore(metaCharset, head.firstChild);
            }
        } else if (metaCharset.getAttribute('charset') !== 'UTF-8') {
            metaCharset.setAttribute('charset', 'UTF-8');
        }
        
        if (GM_getValue('showInfoDisplay', true) === true) {
            const infoScript = doc.createElement('script');
            infoScript.textContent = `
                document.addEventListener('DOMContentLoaded', function() {
                    const infoDiv = document.getElementById('offline-save-info');
                    const detailsDiv = document.getElementById('offline-save-details');
                    const miniInfoDiv = infoDiv ? infoDiv.querySelector('div:not(#offline-save-details)') : null;
                    
                    if (infoDiv && detailsDiv && miniInfoDiv) {
                        infoDiv.addEventListener('mouseenter', function() {
                            this.style.opacity = '1';
                        });
                        
                        infoDiv.addEventListener('mouseleave', function() {
                            if (detailsDiv.style.display === 'none') {
                                this.style.opacity = '0.5';
                            }
                        });
                        
                        infoDiv.addEventListener('click', function(e) {
                            e.stopPropagation ? e.stopPropagation() : (e.cancelBubble = true);
                            
                            if (e.target.id === 'remove-info-bar') {
                                e.preventDefault ? e.preventDefault() : (e.returnValue = false);
                                this.remove();
                                return;
                            }
                            
                            if (detailsDiv.style.display === 'none') {
                                detailsDiv.style.display = 'block';
                                miniInfoDiv.textContent = '↓ 收起信息';
                                this.style.opacity = '1';
                            } else {
                                detailsDiv.style.display = 'none';
                                miniInfoDiv.textContent = '↑ 离线页面信息';
                                this.style.opacity = '0.5';
                            }
                        });
                        
                        const allLinks = detailsDiv.querySelectorAll('a');
                        for (const link of allLinks) {
                            link.addEventListener('click', function(e) {
                                e.stopPropagation ? e.stopPropagation() : (e.cancelBubble = true);
                            });
                        }
                        
                        const closeButton = document.getElementById('remove-info-bar');
                        if (closeButton) {
                            closeButton.addEventListener('click', function(e) {
                                e.stopPropagation ? e.stopPropagation() : (e.cancelBubble = true);
                                e.preventDefault ? e.preventDefault() : (e.returnValue = false);
                                infoDiv.remove();
                            });
                        }
                    }
                });
            `;
            
            const head = doc.head || doc.getElementsByTagName('head')[0];
            if (head) {
                head.appendChild(infoScript);
            }
        }
        
        const htmlContent = doc.documentElement.outerHTML;
        return '<!DOCTYPE html>\n' + htmlContent;
    }

    // 清理文件名
    function sanitizeFilename(name) {
        let safeName = name.replace(/[\\/:*?"<>|]/g, '_');
        safeName = safeName.trim();
        if (!safeName) {
            safeName = 'webpage';
        }
        return safeName;
    }

    // 获取资源内容
    function fetchResource(url) {
        return new Promise((resolve, reject) => {
            const requestTimeout = setTimeout(() => {
                reject(new Error(`请求超时: ${url}`));
            }, 15000);
            
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                timeout: 15000,
                headers: {
                    'Accept': 'text/css,*/*',
                    'Referer': document.location.href
                },
                anonymous: true,
                onload: function(response) {
                    clearTimeout(requestTimeout);
                    if (response.status >= 200 && response.status < 300) {
                        resolve(response.responseText);
                    } else {
                        reject(new Error(`获取资源失败: ${response.status} ${response.statusText}`));
                    }
                },
                onerror: function(error) {
                    clearTimeout(requestTimeout);
                    reject(new Error(`网络错误: ${error}`));
                },
                ontimeout: function() {
                    clearTimeout(requestTimeout);
                    reject(new Error(`请求超时: ${url}`));
                }
            });
        });
    }

    // 获取资源并转换为base64
    function fetchResourceAsBase64(url) {
        return new Promise((resolve, reject) => {
            const requestTimeout = setTimeout(() => {
                reject(new Error(`请求超时: ${url}`));
            }, 15000);
            
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                responseType: 'arraybuffer',
                timeout: 15000,
                headers: {
                    'Accept': 'image/*,*/*',
                    'Referer': document.location.href
                },
                anonymous: true,
                onload: function(response) {
                    clearTimeout(requestTimeout);
                    if (response.status >= 200 && response.status < 300) {
                        try {
                            const bytes = new Uint8Array(response.response);
                            const binary = bytes.reduce((acc, byte) => acc + String.fromCharCode(byte), '');
                            const base64 = btoa(binary);
                            
                            let mimeType = 'application/octet-stream';
                            const contentType = response.responseHeaders.match(/content-type:\s*([^\s;]+)/i);
                            if (contentType && contentType[1]) {
                                mimeType = contentType[1].toLowerCase();
                            } else {
                                const extension = url.split('.').pop().toLowerCase();
                                const mimeTypes = {
                                    'jpg': 'image/jpeg',
                                    'jpeg': 'image/jpeg',
                                    'png': 'image/png',
                                    'gif': 'image/gif',
                                    'svg': 'image/svg+xml',
                                    'webp': 'image/webp',
                                    'woff': 'font/woff',
                                    'woff2': 'font/woff2',
                                    'ttf': 'font/ttf',
                                    'eot': 'application/vnd.ms-fontobject',
                                    'otf': 'font/otf',
                                    'css': 'text/css',
                                    'js': 'application/javascript'
                                };
                                if (extension in mimeTypes) {
                                    mimeType = mimeTypes[extension];
                                }
                            }
                            
                            resolve(`data:${mimeType};base64,${base64}`);
                        } catch (error) {
                            reject(new Error(`处理资源数据时出错: ${error.message}`));
                        }
                    } else {
                        reject(new Error(`获取资源失败: ${response.status} ${response.statusText}`));
                    }
                },
                onerror: function(error) {
                    clearTimeout(requestTimeout);
                    reject(new Error(`网络错误: ${error}`));
                },
                ontimeout: function() {
                    clearTimeout(requestTimeout);
                    reject(new Error(`请求超时: ${url}`));
                }
            });
        });
    }

    // 初始化函数
    function init() {
        const isEnabled = GM_getValue(SETTING_KEY, true);
        const { container } = createFloatingButton();
        
        if (container) {
            container.classList.toggle('hidden', !isEnabled);
        }
        
        // 注册菜单命令
        GM_registerMenuCommand('📱 显示/隐藏浮动按钮', () => {
            const currentValue = GM_getValue(SETTING_KEY, true);
            GM_setValue(SETTING_KEY, !currentValue);
            
            const message = !currentValue ? '已开启浮动按钮' : '已关闭浮动按钮';
            const notification = document.createElement('div');
            notification.style.cssText = `
                position: fixed;
                bottom: 20px;
                left: 20px;
                background: rgba(0, 0, 0, 0.7);
                color: white;
                padding: 10px 20px;
                border-radius: 5px;
                z-index: 10001;
                font-size: 14px;
                transition: opacity 0.3s;
            `;
            notification.textContent = message;
            document.body.appendChild(notification);
            
            setTimeout(() => {
                notification.style.opacity = '0';
                setTimeout(() => notification.remove(), 300);
            }, 3000);
        });
        
        // 监听设置变化
        GM_addValueChangeListener(SETTING_KEY, (name, oldValue, newValue) => {
            const container = document.getElementById('offline-saver-container');
            if (container) {
                container.classList.toggle('hidden', !newValue);
            }
        });
    }
    
    // 初始化
    init();
})();